﻿// License Notification
//======================================================
// Copyright (c) 2010 Channel Intelligence Inc.
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// ======================================================




using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using CIP4.Common;
using CIP4.Item;
using CIP4.RulesEngine;
using MassTransit;
using MassTransit.Internal;
using CIP4.Simple;

namespace CIP4.DataProvider.MassTransit
{
    public class RequestMessageConsumer : Consumes<RequestMessage>.All, IBusService
    {
        private IServiceBus _bus;
        private UnsubscribeAction _unsubscribeAction;

        private Dictionary<string, IDoReqest> _doRequests;

        public void Consume(RequestMessage message)
        {
            try
            {
                RequestMessageReceiver.Logger(MajorEnum.Trace, MinorEnum.Generic, "Request message received: {0}",
                    new object[] {message});
                switch (message.Operation)
                {
                    case RequestMessage.OperationEnum.GetItems:
                        _doRequests[message.ExpectedType].GetItems(MakeExpression(message.SelectionExpression), message);
                        break;
                    case RequestMessage.OperationEnum.SaveItems:
                        _doRequests[message.ExpectedType].SaveItems(message.Values, message);
                        break;
                    case RequestMessage.OperationEnum.PurgeItems:
                        _doRequests[message.ExpectedType].PurgeItems(MakeExpression(message.SelectionExpression), message);
                        break;
                    case RequestMessage.OperationEnum.DeleteCollection:
                        _doRequests[message.ExpectedType].DeleteCollection(message);
                        break;
                    default:
                        throw new ApplicationException(string.Format("Unknown operation type: {0}", message));
                }
            }
            catch (Exception ex)
            {
                RequestMessageReceiver.Logger(MajorEnum.Error, MinorEnum.Generic, "Error processing Request: {0} - {1}",
                    new object[] { message, ex }); 
                throw;
            }
        }

        public void Dispose()
        {
            _bus.Dispose();
        }

        public void Start(IServiceBus bus)
        {
            _bus = bus;
            _unsubscribeAction = bus.Subscribe(this);
            throw new ApplicationException("Should not call this...");
        }

        public void Start(IServiceBus bus, Assembly withAssembly, Dictionary<string, CIP4ConnectionAttribute> connections)
        {
            MakeRequests(withAssembly, connections);
            _bus = bus;
            _unsubscribeAction = bus.Subscribe(this);
        }

        public void Stop()
        {
            _unsubscribeAction();
        }

        private void MakeRequests(Assembly forAssembly, Dictionary<string, CIP4ConnectionAttribute> connections)
        {
            _doRequests = new Dictionary<string, IDoReqest>();
            foreach (Type t in forAssembly.GetTypes())
            {
                if (CIP4ItemAttribute.GetAttribute(t) != null)
                {
                    Type doRequestType = typeof(DoReqest<>).MakeGenericType(t);
                    ConstructorInfo cons = doRequestType.GetConstructor(new[] { typeof(Dictionary<string, CIP4ConnectionAttribute>) });
                    IDoReqest idor = (IDoReqest)cons.Invoke(new object[] { connections });
                    _doRequests.Add(t.Name, idor);
                    RequestMessageReceiver.Logger(MajorEnum.Trace, MinorEnum.Generic,
                        "Found type {0}", new object[] { t.Name });
                }
            }
        }

        static IBooleanValue MakeExpression(string expressionString)
        {
            if (string.IsNullOrEmpty(expressionString))
                return null;

            return (IBooleanValue)CIValueType.Initialize(XElement.Parse(expressionString));
        }
    }

    public interface IDoReqest
    {
        void SaveItems(List<object> items, RequestMessage message);
        void PurgeItems(IBooleanValue selectionExpression, RequestMessage message);
        void GetItems(IBooleanValue selectionExpression, RequestMessage message);
        void DeleteCollection(RequestMessage message);
    }

    public class DoReqest<T> : IDoReqest where T: class
    {
        private readonly StrongWrapper<T> _wrapper;

        public DoReqest(Dictionary<string, CIP4ConnectionAttribute> connections)
        {
            CIP4ItemAttribute att = CIP4ItemAttribute.GetAttribute(typeof (T));
            string collectionName = string.IsNullOrEmpty(att.Collection) ? 
                string.IsNullOrEmpty(att.ActualName)? typeof(T).Name: att.ActualName : att.Collection;
            if (connections.ContainsKey(collectionName))
            {
                _wrapper = new StrongWrapper<T>(connections[collectionName], RequestMessageReceiver.Logger);
            }
            else
            {
                _wrapper = new StrongWrapper<T>(RequestMessageReceiver.Logger);
                RequestMessageReceiver.Logger(MajorEnum.Warning, MinorEnum.NoAvailableConnection, "There is no connection available for the {0} collection of type {1}", new object[] { collectionName, typeof(T).Name });
            }
        }

        public void SaveItems(List<object> items, RequestMessage message)
        {
            List<object> responses = new List<object>();

            MajorEnum worstCase = MajorEnum.Noise;

            LoggerDelegate logger =
                delegate(MajorEnum status, MinorEnum minorStatus, string statusMessage, object[] paras)
                {
                    RequestMessageReceiver.Logger(status, minorStatus, statusMessage, paras);
                    if (status <= MajorEnum.Information)
                        responses.Add(
                            new ResponseMessage
                            {
                                Status = status,
                                MinorStatus = minorStatus,
                                Message = statusMessage,
                                Paras = DeReferenceValues(paras)
                            });
                    if (status < worstCase)
                        worstCase = status;
                    return LoggerResponseEnum.keepGoing;
                };

            string whoIsHe = message.CorrelationTag;
            Guid correlationId = message.CorrelationId;

            RequestMessageReceiver.Logger(
                MajorEnum.Trace, MinorEnum.Generic, "Saving {0}",
                new object[] { ListText(message.Values) });

            List<T> itemsList = items.Cast<T>().ToList();
            _wrapper.SaveItems(itemsList, logger);

            CurrentMessage.Respond(
                new ResponseMessage
                {
                    CorrelationTag = whoIsHe,
                    CorrelationId = correlationId,
                    Status = MajorEnum.Trace,
                    MinorStatus = MinorEnum.SaveItems,
                    Message = "Saved items",
                    Paras = responses
                });
        }

        public void PurgeItems(IBooleanValue selectionExpression, RequestMessage message)
        {
            List<object> responses = new List<object>();

            MajorEnum worstCase = MajorEnum.Noise;

            LoggerDelegate logger =
                delegate(MajorEnum status, MinorEnum minorStatus, string statusMessage, object[] paras)
                {
                    RequestMessageReceiver.Logger(status, minorStatus, statusMessage,paras);

                    if (status <= MajorEnum.Information)
                        responses.Add(
                            new ResponseMessage
                            {
                                Status = status,
                                MinorStatus = minorStatus,
                                Message = statusMessage,
                                Paras = DeReferenceValues(paras)
                            });
                    if (status < worstCase)
                        worstCase = status;
                    return LoggerResponseEnum.keepGoing;
                };

            string whoIsHe = message.CorrelationTag; 
            Guid correlationId = message.CorrelationId;

            RequestMessageReceiver.Logger(
                MajorEnum.Trace, MinorEnum.Generic, "Deleting {0}",
                new object[] {ListText(message.Values)});

            List<ExternalIDType> ids = message.Values.Cast<string>().Select(id => new ExternalIDType(id)).ToList();

            _wrapper.PurgeItems(ids, logger);

            CurrentMessage.Respond(
                new ResponseMessage
                {
                    CorrelationTag = whoIsHe,
                    CorrelationId = correlationId,
                    Status = MajorEnum.Trace,
                    MinorStatus = MinorEnum.PurgeItems,
                    Message = "Purge items",
                    Paras = responses
                });
        }

        public void DeleteCollection(RequestMessage message)
        {
            List<object> responses = new List<object>();

            MajorEnum worstCase = MajorEnum.Noise;

            LoggerDelegate logger =
                delegate(MajorEnum status, MinorEnum minorStatus, string statusMessage, object[] paras)
                {
                    RequestMessageReceiver.Logger(status, minorStatus, statusMessage, paras);
                    if (status <= MajorEnum.Information)
                        responses.Add(
                            new ResponseMessage
                            {
                                Status = status,
                                MinorStatus = minorStatus,
                                Message = statusMessage,
                                Paras = DeReferenceValues(paras)
                            });
                    if (status < worstCase)
                        worstCase = status;
                    return LoggerResponseEnum.keepGoing;
                };

            string whoIsHe = message.CorrelationTag;
            Guid correlationId = message.CorrelationId;

            _wrapper.DeleteCollection(logger);

            CurrentMessage.Respond(
                new ResponseMessage
                {
                    CorrelationTag = whoIsHe,
                    CorrelationId = correlationId,
                    Status = MajorEnum.Trace,
                    MinorStatus = MinorEnum.DeleteCollection,
                    Message = "delete collection",
                    Paras = responses
                });
        }

        public void GetItems(IBooleanValue selectionExpression, RequestMessage message)
        {
            List<object> responses = new List<object>();

            MajorEnum worstCase = MajorEnum.Noise;

            LoggerDelegate logger =
                delegate(MajorEnum status, MinorEnum minorStatus, string statusMessage, object[] paras)
                {
                    RequestMessageReceiver.Logger(status, minorStatus, statusMessage, paras);
                    if (status <= MajorEnum.Information)
                        responses.Add(
                            new ResponseMessage
                            {
                                Status = status,
                                MinorStatus = minorStatus,
                                Message = statusMessage,
                                Paras = DeReferenceValues(paras)
                            });
                    if (status < worstCase)
                        worstCase = status;
                    return LoggerResponseEnum.keepGoing;
                };

            string whoIsHe = message.CorrelationTag; 
            Guid correlationId = message.CorrelationId;

            List<object> result = new List<object>();
            foreach (T item in _wrapper.GetItems(selectionExpression, logger))
                result.Add(item);

            RequestMessageReceiver.Logger(
                MajorEnum.Trace, MinorEnum.Generic, 
                "Sending response message with {0} items for {1} selecting {2}", 
                new object[] {result.Count, whoIsHe, selectionExpression == null? "all": selectionExpression.ToString()});

            CurrentMessage.Respond(
                new ResponseMessage
                {
                    CorrelationTag = whoIsHe,
                    CorrelationId = correlationId,
                    Status = MajorEnum.Trace, 
                    MinorStatus = MinorEnum.GetItems,
                    Message = "Get Items",
                    Paras = responses,
                    Values = result
                });
        }

        private static List<object> DeReferenceValues(object[] paras)
        {
            // idea is to force the evaluation of any expressions so the 
            // bus doodad doesn't try to serialize the expression itself
            List<object> result = new List<object>();
            foreach (object value in paras)
                result.Add(value.ToString());
            return result;
        }

        private static string ListText(List<object> items)
        {
            if (items == null)
                return "null list";
            string text = string.Empty;
            foreach (object value in items)
            {
                text += text.Seperator(",") + value.ToString();
            }
            return string.Format("{0}{{{1}}}", items.Count, text);
        }

    }
}
